const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {

    constructor(executor) {
        // constructor方法是类的构造函数的默认方法，通过new命令生成对象实例时，自动调用该方法
        // 即通过new操作符,构造方法(constructor)是被自动调用,的，这意味着在构造方法中我们可以做一些初始化的工作。

        // 执行器是立即执行的
        // executor(this.resolve, this.reject)

        // 捕获错误（两个地方）:1、在执行构造器时 2、在执行then那个回调函数的时候
        // 在构造函数中调用了这个执行器（现在要弄处理错误）,要把执行器放到try当中
        try {
            // 我们要尝试去执行这个执行器，如果执行器发生错误，会走到catch这个地方 e是错误对象
            executor(this.resolve, this.reject)
        } catch (e) {
            // 如果代码走到了catch，让状态变成错误的状态.把错误的原因传递过去
            this.reject(e)
        }

    }

    // promise状态
    status = PENDING;

    // 这两个属性我们要把它定义成实例属性，即声明两个属性
    // 1、成功之后的值，默认成功之后的值是没有的
    value = undefined;
    // 2、失败之后的原因
    reason = undefined;

    // 添加属性
    // 找到成功回调和失败回调，默认值改为数组，数组才能同时存储多个回调函数

    // 成功回调
    // successCallback = undefined;
    successCallback = [];
    // 失败回调
    // failCallback = undefined;
    failCallback = [];


    // 用箭头函数使this指向这个类的实例对象 即promise对象
    resolve = value => {
        // 如果状态不是等待，阻止程序向下执行，只有状态时等待时，才可以去更改
        if (this.status !== PENDING) return;
        // 将状态改为成功
        this.status = FULFILLED;
        // 保存成功之后的值，因为要在then方法中拿到这个值
        this.value = value;

        // 我们要判断成功回调是否存在，如果存在就调用，成功过后的值记得要传递过去
        // this.successCallback && this.successCallback(this.value);

        // 改成了数组，里面有多个回调函数
        while (this.successCallback.length) {
            // shift方法的返回值就是我们所需要执行的回调函数，在后面直接()去调用
            // this.successCallback.shift()(this.value);
            this.successCallback.shift()();
        }

    }

    reject = reason => {
        if (this.status !== PENDING) return;
        // 判断状态，将状态改为失败
        this.status = REJECTED;
        // 保存失败之后的原因
        this.reason = reason;

        // 我们要判断失败回调是否存在，如果存在就调用，原因也要记得传递过去
        // this.failCallback && this.failCallback(this.reason);

        // 由于shift方法是从数组中删除值，那么我们每执行一个删除一个，那么最终length会变成0，此时没有回调函数了，循环条件也不成立了
        // 即循环体就停止执行了
        // while (this.failCallback.length) this.failCallback.shift()(this.reason);

        while (this.failCallback.length) this.failCallback.shift()();

    }

    // then方法接收两个参数（成功回调&失败回调）
    then(successCallback, failCallback) {

        // 当then中没有参数的时候，我们可以做一个判断，然后把值也返回下去。看successCallback是否存在
        // 如果存在，就用successCallback。如果不存在，就补这个函数进来
        successCallback = successCallback ? successCallback : value => value;
        // 下面失败的callback同理
        failCallback = failCallback ? failCallback : reason => { throw reason };

        // 要返回promise对象，就要创建promise对象
        let promise2 = new MyPromise((resolve, reject) => {
            // 在MyPromise这添加两个参数，当调用resolve这个方法时，实际上时把这个值传递给下一个then方法的回调函数


            // 把下面这一对放进promise执行器里面
            // 判断状态，成功就调用成功回调，失败就调用失败回调
            // 在调用成功/失败回调时，要把值/原因传递给回调函数，这样promise的调用者才可以拿到promise执行的结果
            if (this.status === FULFILLED) {
                // 当你调用这个方法的时候 应该把成功之后的*值传递给它，失败同理
                // 有保存，当你调用then方法时 就可以拿到值，就可以传值

                // 声明一个变量，拿到它的返回值。然后把这个返回值传递给下一个then它的成功回调函数

                setTimeout(() => {

                    // 如果then第一个回调函数报错的话，现在要捕获,这样就可以捕获到成功回调的执行错误了
                    try {

                        let x = successCallback(this.value)
                        // 判断x的值是普通值还是promise对象
                        // 如果是普通值 直接调用resolve，如果是promise对象 查看promise对象返回的结果
                        // （即如果返回的结果是成功，调用resolve，把成功的结果传递给下一个promise对象。若返回结果是失败，调用reject把失败原因传递给下一个promise对象）
                        // 再根据promise对象返回的结果 决定调用resolve还是reject(要判断)
                        resolvePromise(promise2, x, resolve, reject)
                        // 由于这里的promise2是实例过程声明的，所以拿不到。只需要把这部分代码变成异步的就OK了
                        // 等所有同步代码执行后再执行，那就可以获取到了promise2

                    } catch (e) {
                        // 如果这个代码在执行的过程中发生了错误，就手动去调用下一个promise的reject方法
                        // 我们要把错误信息传递给下一个promise的回调函数
                        reject(e);
                    }

                }, 0)


                // resolve(x);
                // 这样的话，下一个then方法的回调函数就可以拿到这个值
            } else if (this.status === REJECTED) {
                // then方法这里处理链式调用时，当状态是失败时或异步的时候，也要去处理一下.也要settimeout
                setTimeout(() => {
                    try {
                        let x = failCallback(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                }, 0)

                // failCallback(this.reason)
            } else {
                // 由于等待pending不能马上调用，要把这俩（成功&失败）先存起来
                // 将成功回调和失败回调存储起来,如果等待的话，让
                // this.successCallback = successCallback;
                // this.failCallback = failCallback;

                // this.successCallback只是一个普通的属性它每次只能存储1个回调函数，此时不符合我们的需求（多个then）
                // this.successCallback.push(successCallback);
                // this.failCallback.push(failCallback);


                // 把上面的改成：在这要push一个函数进去
                this.successCallback.push(() => {
                    // successCallback();

                    // 把上面成功的代码复制下来，如下：（上面那个就可以不要了，下面那个失败同理）
                    setTimeout(() => {
                        // 如果then第一个回调函数报错的话，现在要捕获,这样就可以捕获到成功回调的执行错误了
                        try {
                            let x = successCallback(this.value)
                            // 判断x的值是普通值还是promise对象
                            // 如果是普通值 直接调用resolve，如果是promise对象 查看promise对象返回的结果
                            // （即如果返回的结果是成功，调用resolve，把成功的结果传递给下一个promise对象。若返回结果是失败，调用reject把失败原因传递给下一个promise对象）
                            // 再根据promise对象返回的结果 决定调用resolve还是reject(要判断)
                            resolvePromise(promise2, x, resolve, reject)
                            // 由于这里的promise2是实例过程声明的，所以拿不到。只需要把这部分代码变成异步的就OK了
                            // 等所有同步代码执行后再执行，那就可以获取到了promise2

                        } catch (e) {
                            // 如果这个代码在执行的过程中发生了错误，就手动去调用下一个promise的reject方法
                            // 我们要把错误信息传递给下一个promise的回调函数
                            reject(e);
                        }

                    }, 0)
                });
                this.failCallback.push(() => {
                    // failCallback();
                    setTimeout(() => {
                        try {
                            let x = failCallback(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)

                });
                // 由此上面的while就不用传this.value值了


            }
        });

        // then方法返回promise对象，可以实现链式调用了
        return promise2;

    }



    finally(callback) {
        // 当前这个方法接收一个回调函数作为参数，因为无论当前这个promise的状态是成功/失败，都要去调用它
        // 要怎样得到当前这个promise对象的状态呢？then方法可以知道这个状态
        // 在finally方法的内部，我们要调用this.then得到当前promise对象的状态
        return this.then(value => {

            // 找到MyPromise这个类下面resolve方法，把callback这个函数调用的结果传递过去，不管你返回的是promise还是普通值，都转成promise
            // 然后去执行这个promise，在then方法的成功回调函数返回这个value
            // 这样就可以等待callback里面返回的这个promise它去执行
            return MyPromise.resolve(callback()).then(() => value);


            // 把下面代码去掉，还要把上面那个的最终结果返回出去
            // callback();
            // return value;
        }, reason => {
            // 失败同理
            return MyPromise.resolve(callback()).then(() => {
                // 这个reason是上面形参reason
                throw reason
            });

            // callback();
            // throw reason;
        })
        // 在finally方法的后面要链式调用then方法，所以finally方法最终也要返回一个promise对象。
        // 我们在finally内部调用了then，然而then返回的就是promise对象，通过return把then方法返回，这样finally后面就可以再次调用then方法
        // 再次调用then方法的时候还需要拿到当前这个promise的结果(return了value，下一个then方法才能拿到这个value，失败同理)

    }

    // 加入1个实例方法，被定义在原型对象中
    catch(failCallback) {
        this.then(undefined, failCallback)
        // 在then后面也可以链式调用promise对象的其他方法
    }


    // 定义一个静态方法 在前面要加上关键字static。声明all方法是静态方法
    static all(array) {
        // 声明一个结果数组
        let result = [];

        let index = 0;

        return new MyPromise((resolve, reject) => {

            // 每次调用addData，我就让index++,当index++完成之后，判断index的值和array的长度是否相等
            // 相等：证明数组中所有的内容都执行完了，所有的异步操作都执行完了，此时可以调用resolve方法去结束all这个方法的状态了
            // 并且把result这个结果数组给它传递出去
            function addData(key, value) {
                // 要在当前这个函数中拿到这个数组
                // 把下标为key的元素添加到result的数组当中
                result[key] = value;
                index++;
                if (index === array.length) {
                    // 相等才去调用resolve，这样就能够保证all方法里的array中的每一项都已经执行完了
                    resolve(result);
                }
            }
            // 还要把addData放到myPromise的执行器中，不放的话，就拿不到那个resolve啦！！

            // 循环这个数组，判断是普通值还是promise对象
            // 1、普通值，直接把这个值放到结果数组当中
            // 2、promise对象：先去执行这个promise对象，然后再把promise对象的结果放到结果数组当中
            for (let i = 0; i < array.length; i++) {
                let current = array[i];
                // 判断current是否是Mypromise类下面的一个实例
                if (current instanceof MyPromise) {
                    // 是：promise对象，先去执行promise对象
                    // 给它传递一个成功回调和一个失败回调，如果走到成功回调里，直接把结果value添加到result里
                    // 如果走到了失败回调里，直接调用reject，如果有1个失败，就让当前这个all方法它返回的这个状态时失败的，直接调用reject方法
                    current.then((value) => addData(i, value), reason => reject(reason))
                } else {
                    // 否：就是普通值
                    // 把下标为i的那个值添加到result数组中
                    addData(i, array[i])
                }
            }

            // 当循环执行完之后，就可以去调用resolve方法，并且把result传递到外面去
            // resolve(result);

        })

        // ***这里开启了for循环，for循环在一瞬间就执行完了。但是执行for循环过程中是有异步操作的。比如我们传递过来的p1就是异步操作
        // 那么这个循环是没有等待异步操作的。当这个循环完成之后，我们直接调用了resolve结束了这个状态
        // 当调用resolve，此时p1里面的代码没有执行完，就调用resolve了，所以在结果中会出现一个空值
        // 解决方案 声明一个index
        // ***要等待所有的异步操作完成之后 才能去调用resolve方法去结束all方法这个状态.

    }



    // resolve是静态方法
    static resolve(value) {
        // 判断是普通值还是promise对象
        // value是否是MyPromise类的实例对象

        // 如果是的话就是promise对象，直接把他返回就可以了
        if (value instanceof MyPromise) return value;
        // 若不是promise对象，是普通值，我们就要创建一个promise对象,然后传递一个执行器进去
        // 在执行器中我们要拿到resolve方法，通过resolve方法把value进行返回就可以了
        return new MyPromise(resolve => resolve(value))

    }


}

// 不需要实例化类，即可直接通过该类来调用的方法，即称之为“静态方法”。
// 将类中的方法设为静态方法也很简单，在方法前加上static关键字即可。这样该方法就不会被实例继承！
// ****静态方法只能在静态方法中调用,不能在实例方法中调用。
// 



function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Changing cycle detected for promise #<Promise> 2021'));
    }

    // 要判断x是普通值还是promise对象
    // (只需要判断x是不是MyPromise这个类的实例对象就可以了，如果是那他就是promise对象。否则就是普通值)
    if (x instanceof MyPromise) {
        // promise对象。需要调用它下面的then方法去看查看这个promise状态,并且把状态传递给下一个promise对象
        // 如果这个promise状态是成功的，那么它会走第一个回调函数(调用resolve并且把value传递进去)。失败就走第二个回调函数(调用reject并把失败原因传递下去)
        // x.then( value => resolve(value), reason => reject(reason))
        // 上面这段可以简化成↓的代码：
        x.then(resolve, reject);

    } else {
        // 普通值，直接调用resolve传递过去
        resolve(x);
    }
}



// ↑↑↑实现了一个最最简单的promise，然后验证是否可以使用，将这个类导出
module.exports = MyPromise